package com.minigame.datastore;

import com.minigame.datastore.operation.OperationType;
import com.minigame.exception.GameDataOperationException;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.repository.JpaRepository;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class DataStore {
    private static final Logger logger = LoggerFactory.getLogger(DataStore.class);



    private int batchSize = 100;

    private Thread thread;


    private Map<Class,DataStoreSelect> entityDataStoreMap = new ConcurrentHashMap<>();

    private EntityManager entityManager;

    private volatile boolean stop;

    public DataStore(EntityManager entityManager){
        this.entityManager = entityManager;
        thread = new Thread(()->{
             while (!stop){
                 batchChangeEntityDataStore();
                 batchOperation();
                 try {
                     Thread.sleep(10000);
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 }
             }
            batchOperation();
        });
        thread.start();
    }

    public <ID extends Serializable,T> void insert(JpaRepository jpaRepository,ID id,T t){
        EntityDataStore entityDataStore = getEntityDataStore(jpaRepository,t);
        entityDataStore.insert(id,t);
    }

    public <ID extends Serializable,T> void update(JpaRepository jpaRepository,ID id,T t){
        EntityDataStore entityDataStore = getEntityDataStore(jpaRepository,t);
        entityDataStore.update(id,t);
    }

    public <ID extends Serializable,T> void delete(JpaRepository jpaRepository,ID id,T t){
        EntityDataStore entityDataStore = getEntityDataStore(jpaRepository,t);
        entityDataStore.delete(id,t);
    }




    private <ID extends Serializable,T> EntityDataStore<T,ID> getEntityDataStore(JpaRepository jpaRepository,T t){
        Class<?> cls = t.getClass();
        DataStoreSelect dataStoreSelect = entityDataStoreMap.get(cls);
        if(dataStoreSelect == null){
            synchronized (cls){
                dataStoreSelect = entityDataStoreMap.get(cls);
                if(dataStoreSelect == null){
                    EntityDataStore oldEntityDataStore = new EntityDataStore<>(jpaRepository, cls.getName());
                    EntityDataStore newEntityDataStore = new EntityDataStore<>(jpaRepository, cls.getName());
                    dataStoreSelect = new DataStoreSelect(oldEntityDataStore,newEntityDataStore);
                    entityDataStoreMap.put(cls,dataStoreSelect);
                }
            }
        }
        return dataStoreSelect.getEntityDataStore();
    }


    public void batchOperation(){
        Iterator<Map.Entry<Class,DataStoreSelect>> iterator = entityDataStoreMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Class,DataStoreSelect> entry = iterator.next();
            EntityDataStore<?,?> entityDataStore = entry.getValue().getEntityDataStore2DB();
            batchPriorityDeleteData(entityDataStore,entityDataStore.getPriorityDeleteMap());
            batchInsertData(entityDataStore,entityDataStore.getInsertMap());
            batchUpdateData(entityDataStore,entityDataStore.getUpdateMap());
            batchDeleteData(entityDataStore,entityDataStore.getDeleteMap());
        }
    }

    public void batchChangeEntityDataStore(){
        Iterator<Map.Entry<Class,DataStoreSelect>> iterator = entityDataStoreMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry<Class,DataStoreSelect> entry = iterator.next();
            entry.getValue().changeEntityDataStore();
        }
    }



    public void batchInsertData(EntityDataStore entityDataStore,Map insertMap){
        if(insertMap.size() <= 0){
            return;
        }

        Iterator<Map.Entry> iterator = insertMap.entrySet().iterator();
        List dataList = entityDataStore.getDataList();
        while (iterator.hasNext()){
            Map.Entry entry = iterator.next();
            dataList.add(entry.getValue());
            iterator.remove();
            if(dataList.size() >= batchSize){
                batchInsertData(dataList);
            }
        }
        if(dataList.size() > 0){
            batchInsertData(dataList);
        }

    }

    public void batchDeleteData(EntityDataStore entityDataStore,Map deleteMap){
        if(deleteMap.size() <= 0){
            return;
        }
        Iterator<Map.Entry> iterator = deleteMap.entrySet().iterator();
        List idList = entityDataStore.getIdList();
        while (iterator.hasNext()){
            Map.Entry entry = iterator.next();
            idList.add(entry.getKey());
            iterator.remove();

            if(idList.size() >= batchSize){
                batchIDDeleteData(entityDataStore.getJpaRepository(),idList);
            }
        }
        if(idList.size() > 0){
            batchIDDeleteData(entityDataStore.getJpaRepository(),idList);
        }
    }

    public void batchUpdateData(EntityDataStore entityDataStore,Map updateMap){
        if(updateMap.size() <= 0){
            return;
        }
        List dataList = entityDataStore.getDataList();
        Iterator<Map.Entry> iterator = updateMap.entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry entry = iterator.next();
            dataList.add(entry.getValue());
            iterator.remove();
            if(dataList.size() >= batchSize){
                batchUpdateData(dataList);
            }
        }
        if(dataList.size() > 0){
            batchUpdateData(dataList);
        }
    }




    public  void batchPriorityDeleteData(EntityDataStore entityDataStore,Map priorityDeleteMap){
        if(priorityDeleteMap.size() <= 0){
            return;
        }
        Iterator<Map.Entry> iterator = priorityDeleteMap.entrySet().iterator();
        List idList = entityDataStore.getIdList();
        while (iterator.hasNext()){
            Map.Entry entry = iterator.next();
            idList.add(entry.getKey());
            iterator.remove();

            if(idList.size() >= batchSize){
                batchIDDeleteData(entityDataStore.getJpaRepository(),idList);
            }
        }
        if(idList.size() > 0){
            batchIDDeleteData(entityDataStore.getJpaRepository(),idList);
        }

    }

    private  void batchIDDeleteData(JpaRepository jpaRepository,List list){
        try {
            jpaRepository.deleteAllByIdInBatch(list);
        }
        catch (Exception ex){
            Object[] array = new Object[list.size()];
            list.toArray(array);
            logger.error("batchIDDeleteData exception="+ex.getMessage()+" ids="+ Arrays.toString(array),ex);
        }
        finally {
            list.clear();
        }
    }


    private <T> void batchInsertData(List<T> list){
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            for(T t : list){
                entityManager.persist(t);
            }
        }
        catch (Exception e){
            logger.error(e.getMessage(),e);
        }
        finally {
            list.clear();
            entityManager.flush();
            entityManager.clear();
            transaction.commit();
        }

    }


    private void batchUpdateData(List list){
        EntityTransaction transaction = entityManager.getTransaction();
        transaction.begin();
        try {
            for(Object t : list){
                entityManager.merge(t);
            }
        }
        catch (Exception e){
            logger.error(e.getMessage(),e);
        }
        finally {
            list.clear();
            entityManager.flush();
            entityManager.clear();
            transaction.commit();
        }

    }

    public boolean isStop() {
        return stop;
    }

    public void setStop(boolean stop) {
        this.stop = stop;
    }
}
